PostgreSQL amcheck

1 背景知识

amcheck 扩展模块可以验证数据库对象的逻辑一致性,常用于索引对象是否有效。

1.1 amcheck 扩展模块原理

amcheck 扩展模块主要是查询对象中的各种常量判断对象的有效性。因为执行计划相关的操作方法的正确性是依赖于这些常量的正确性。

1、例如:验证 Btree 页面中是否按照“逻辑”顺序排列。
2、如果“逻辑”顺序混乱时,将无法正确的进行索引扫描,导致SQL 查询到错误答案。
3、例如下图,branch 节点,第二节点数据损坏,将无法进行索引扫描。

heap tuples
(record data)
heap tuples...
PGAGEhader
PGAGEhader
pd_special
pd_special
1
1
2
2
itup 0
itup 0
free space
(hole)
free space...
t_tid
t_tid
t_info
t_info
ip_blkid
ip_blkid
ip_posid
ip_posid
values
values
Bitmap
Bitmap
Special Space
Special Space
btpo_prev
btpo_prev
btpo_next
btpo_next
btpo_level
btpo_level
btpo_flags
btpo_flags
btpo_cycleid
btpo_cycle...
heap tuples
(record data)
heap tuples...
PGAGEhader
PGAGEhader
pd_special
pd_special
1
1
2
2
itup 0
itup 0
free space
(hole)
free space...
t_tid
t_tid
t_info
t_info
ip_blkid
ip_blkid
ip_posid
ip_posid
values
values
Bitmap
Bitmap
Special Space
Special Space
btpo_prev
btpo_prev
btpo_next
btpo_next
btpo_level
btpo_level
btpo_flags
btpo_flags
btpo_cycleid
btpo_cycle...
pd_special
pd_special
pd_special
pd_special
heap tuples
(record data)
heap tuples...
PGAGEhader
PGAGEhader
pd_special
pd_special
0
0
2
2
itup 0
itup 0
free space
(hole)
free space...
t_tid
t_tid
t_info
t_info
ip_blkid
ip_blkid
ip_posid
ip_posid
values
values
Bitmap
Bitmap
Special Space
Special Space
btpo_prev
btpo_prev
btpo_next
btpo_next
btpo_level
btpo_level
btpo_flags
btpo_flags
btpo_cycleid
btpo_cycle...
(0,1)
(0,1)
(3,7)
(3,7)
(0,2)
(0,2)
(0,3) 
(0,3) 
.....
.....
(3,1)
(3,1)
.....
.....
(3,6)
(3,6)
itup 364
itup 364
itup 1
itup 1
.............
.............
itup 1
itup 1
.............
.............
itup 27
itup 27
 (82,71)
 (82,71)
(84,4)
(84,4)
 (82,72)
 (82,72)
 (82,73)
 (82,73)
.....
.....
(84,3)
(84,3)
itup 1
itup 1
.............
.............
itup 172
itup 172
heap tuples
(record data)
heap tuples...
PGAGEhader
PGAGEhader
pd_special
pd_special
1
1
2
2
itup 0
itup 0
free space
(hole)
free space...
t_tid
t_tid
t_info
t_info
ip_blkid
ip_blkid
ip_posid
ip_posid
values
values
Bitmap
Bitmap
Special Space
Special Space
btpo_prev
btpo_prev
btpo_next
btpo_next
btpo_level
btpo_level
btpo_flags
btpo_flags
btpo_cycleid
btpo_cycle...
(3,8) 
(3,8) 
(6,14)
(6,14)
(3,9)
(3,9)
(3,10)
(3,10)
.....
.....
(6,1)
(6,1)
.....
.....
(6,13)
(6,13)
itup 364
itup 364
itup 1
itup 1
.............
.............
1号块
1号块
2号块
2号块
3号块
3号块
29号块
29号块
SELECT * FROM bt_metap('tab02_pkey');
SELECT * FROM bt_page_items('tab02_pkey',3);
SELECT * FROM bt_page_stats('tab02_pkey',2);
SELECT * FROM bt_metap('tab02_pkey');...
1
1
364
364
2
2
3
3
.....
.....
358
358
.....
.....
363
363
365
( 6d 01) 
365...
728
728
366
366
367
367
.....
.....
715
715
.....
.....
727
727
(6,15)
(6,15)
(9,22)  
(9,22)  
(6,16)
(6,16)
.....
.....
(9,21)
(9,21)
729
(d9 02)
729...
1093
(45 04)
1093...
730
730
.....
.....
1092
1092
heap tuples
(record data)
heap tuples...
PGAGEhader
PGAGEhader
pd_special
pd_special
1
1
2
2
itup 0
itup 0
free space
(hole)
free space...
t_tid
t_tid
t_info
t_info
ip_blkid
ip_blkid
ip_posid
ip_posid
values
values
Bitmap
Bitmap
Special Space
Special Space
btpo_prev
btpo_prev
btpo_next
btpo_next
btpo_level
btpo_level
btpo_flags
btpo_flags
btpo_cycleid
btpo_cycle...
itup 364
itup 364
itup 1
itup 1
.............
.............
4号块
4号块
9829
( 65 26)
9829...
10000
(10 27)
10000...
9830
9830
9831
9831
.....
.....
9999
9999
0
0
0
0
1
1
itup 2
itup 2
0
0
(9,1)
(9,1)
1072
1072
.....
.....
.....
.....
.....
.....
.....
.....
Text is not SVG - cannot display
Note

text 字段的 Btree 索引,索引的元组应该按照词典顺序排列。

1.2 amcheck 安装扩展

su - postgres 
psql -U postgres -testdb 
CREATE EXTENSION amcheck;
\dx+ amcheck

2 bt_index_check 函数

2.1 bt_index_check 简介

1、 bt_index_check 测试(BTREE)索引中的常量是否正确。
2、 bt_index_check 无法验证父子关系的常量。
3、heapallindexedtrue 时将验证所有堆元组是否作为索引中的索引元组存在。

2.2 bt_index_check 加锁

1、执行 bt_index_check 函数时,需要在索引及其所属的表 AccessShareLock
2、这种锁模式与简单 SELECT 语句在关系上所要求的锁模式相同。

2.3 语法定义

bt_index_check(index regclass, heapallindexed boolean) returns void
Note

函数输出的DEBUG日志信息对于某些postgresql 用户非常有用。目前有两个级别:
1、DEBUG1。
2、DEBUG2。
可以使用:SET client_min_messages = DEBUG1; 命令进行设置。

2.4 示例

这个例子中会话将会对 testdb 对10个最大的索引进行验证。
1、对于唯一索引,要求所有的索引元组都存在。
2、由于没有错误,所以被检测的索引都处于“逻辑一致”的状态。

SELECT bt_index_check(index => c.oid, heapallindexed => i.indisunique),
               c.relname,
               c.relpages
FROM pg_index i
JOIN pg_opclass op ON i.indclass[0] = op.oid
JOIN pg_am am ON op.opcmethod = am.oid
JOIN pg_class c ON i.indexrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE am.amname = 'btree' AND n.nspname = 'pg_catalog'
-- Don't check temp tables, which may be from another session:
AND c.relpersistence != 't'
-- Function may throw an error when this is omitted:
AND c.relkind = 'i' AND i.indisready AND i.indisvalid
ORDER BY c.relpages DESC LIMIT 10;

对于上述查询语句,请去官方文档了解具体的表字段含义。
pg_am
pg_opclass
pg_index
pg_class
pg_namespace

3 bt_index_parent_check 函数

3.1 bt_index_parent_check 简介

1、 bt_index_parent_check 函数检查不仅索引中常量,并且还会检查索引的父子常量。
2、当 heapallindexedtrue 时,此函数将会验证索引中所有的元组都是存在的,所有页面都存在则无报错。
3、当 rootdescendtrue 时,此函数将会从 root 页面到 left 页面进行搜索,所有页面都存在则无报错。
4、因 bt_index_parent_check 还会检查父子关系的常量,所以检查范围是 bt_index_check 检查范围的超集。检查范围更全面。

3.2 bt_index_parent_check 加锁

1、执行 bt_index_parent_check 函数时,需要在对象上加一个表集的 ShareLock
2、表级的 ShareLock 锁与 INSERTUPDATEDELETE 命令冲突。
3、ShareLock 也会阻止 VACUUM 清理死亡元组。
4、amcheck函数只能由超级用户使用。

Note

函数运行时保持锁,而不是在整个被包含的事务内保持锁。
bt_index_check 和 bt_index_parent_check 都输出关于验证过程的日志信息,在 DEBUG1 和 DEBUG2 严重性级别。
这些消息提供关于验证过程的详细信息,或许对PostgreSQL的开发人员有作用。
高级用户也许会发现这些信息很有帮助,因为它提供了额外的上下文将验证实际检测的不一致。运行:
在运行验证查询之前的交互式psql会话中,将显示有关验证进度的消息,并具有可管理级别的详细信息。

7、 bt_index_parent_check 不能再备库上执行和使用。

3.3 语法定义

bt_index_parent_check(index regclass, heapallindexed boolean, rootdescend boolean) returns void

3.4 示例

这个例子中会话将会对 testdb 对10个最大的索引进行验证。
1、对于唯一索引,要求所有的索引元组都存在,并且从root 页面都能够寻找到left 页面。
2、由于没有错误,所以被检测的索引都处于“逻辑一致”的状态。

SELECT bt_index_parent_check(index => c.oid, heapallindexed => i.indisunique,rootdescend => i.indisunique),
               c.relname,
               c.relpages
FROM pg_index i
JOIN pg_opclass op ON i.indclass[0] = op.oid
JOIN pg_am am ON op.opcmethod = am.oid
JOIN pg_class c ON i.indexrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE am.amname = 'btree' AND n.nspname = 'pg_catalog'
-- Don't check temp tables, which may be from another session:
AND c.relpersistence != 't'
-- Function may throw an error when this is omitted:
AND c.relkind = 'i' AND i.indisready AND i.indisvalid
ORDER BY c.relpages DESC LIMIT 10;

对于上述查询语句,请去官方文档了解具体的表字段含义。
pg_am
pg_opclass
pg_index
pg_class
pg_namespace

4 verify_heapam 函数

4.1 verify_heapam 简介

检查表的结构损坏,关系中包含无效格式的数据的页,以及逻辑损坏,页在结构上是有效的,但与数据库集群的其他部分不一致。

4.1.1 verify_heapam 参数说明

4.1.2 verify_heapam 输出说明

4.2 语法定义

verify_heapam(relation regclass, on_error_stop boolean, check_toast boolean, skip text, startblock bigint, endblock bigint, blkno OUT bigint, offnum OUT integer, attnum OUT integer, msg OUT text) returns setof record

5 heapallindexed 参数详解

B-Tree 验证函数的 heapallindexed 参数为true时;

5.1 检查过程与原理

1、索引和对应的关联表执行一个额外的验证过程。
2、验证由一个“模拟”CREATE INDEX操作组成,创建一个临时的、内存中的模拟索引。
3、模拟索引对目标索引中的每一个元组“采集指纹”。
4、heapallindexed 验证背后的高层原则是:新建的索引与现有索引需要完全一致。

5.2 heapallindexed 参数对性能的影响

1、heapallindexed 阶段会增加明显的开销:验证的时间通常将会延长几倍。
2、模拟索引的大小以 maintenance_work_mem 为界。
3、为了确保不超过2%的失效概率能检测到不一致,每个元组需要大约2个字节的内存。
3、因为每个元组可用的内存变少,错失一处不一致的概率就会慢慢增加。
4、这种限制方法显著地限制了函数验证的开销,但仅仅略微降低了检测到问题的概率。

6 amechk 扩展模块适用场景

6.1 排序规则的更改

1、text 可排序类型数据的比较必须是不变的(正如用于B-树索引扫描的所有比较必须不变一样),这意味着操作系统排序规则必须保持不变。
2、操作系统排序规则的更新会导致索引失效。
3、Master 节点和 Standby 节点排序顺序的不一致也会导致索引失效。
(1)这可能是因为使用的主操作系统版本不一致。
(2)不一致通常仅出现在 Standby节点上,因此通常也仅能在后备服务器上检测到。

6.2 索引和表之间的结构不一致

6.2.1 损坏原因

1、表的块损坏会导致索引损坏。
2、访问方法代码、排序代码或者事务管理代码中未发现的BUG导致的损坏。
3、开发测试时引入逻辑不一致的新特性。

6.2.2 解决办法

4、进行标准回归测试时持续地调用amcheck函数。

6.3 存储故障

在没有开启校验和的文件系统或者存储子系统故障。

注意,如果在访问块时仅有一次共享缓存命中,验证时amcheck会在检查表示在某个共享内存缓冲区中的页面。因此,amcheck没有必要在验证时检查从文件系统读出的数据。

注意当校验和被启用时,如果一个损坏的块被读取到缓冲区中,amcheck可能会由于校验和失效而产生错误。

6.4 内存故障

PostgreSQL无法提供针对可更正内存错误的保护并且它假定用户使用的是具有工业标准纠错码(ECC)或更好保护技术的RAM。不过,ECC内存通常只能免疫单个位错误,并且不应该假定它能提供对导致内存损坏失效的绝对保护。

7 小结

在执行heapallindexed验证时,通常有大幅增加的机会可以检测单个位错误,因为会测试严格的二元等值并且会在堆中测试被索引属性。

由于有故障的存储硬件,或者相关文件被不相关的软件覆盖或修改,可能会发生结构损坏。 这类损坏也可以通过data page checksums来检测。

格式正确、内部一致并且相对于其内部校验和正确的关系页依然可能包含逻辑损坏。 因此,这类损坏不能被checksums所检测到。 例如包括主表中的toasted值在toast表中缺少相应的条目,以及主表中具有比数据库或集群中最古老的有效Transaction ID更旧的Transaction ID的元组。

在生产系统中已经观察到多个导致逻辑损坏的原因,包括PostgreSQL服务器软件中的缺陷、错误且考虑欠妥的备份和恢复工具,以及用户错误。

在实时生产环境中,表的损坏是最令人是最不欢迎的,并且风险极高。

基于此原因,设计了verify_heapam以在无过度风险的情况下诊断错误。

它不能保证防止后端崩溃的所有原因,因为在严重损坏的系统上,即使执行调用查询也可能不安全。执行对catalog tables的访问,如果编目自身损坏了,也可能会出现问题。

通常,amcheck仅能证明损坏的存在,但它无法证明损坏不存在。